#ifndef _REPEAT_H_
#define _REPEAT_H_

#if _WIN32
#pragma once
#endif

/**
 *
 * 17.03.12 - Steven Hartin - Fixed some basic errors / template issues
 *
 * 01.03.12 - Steven Hartin - Original Creation.
 * 
 * USAGE:
 *   CRepeat<return_type> myRepeat(<delay>, <calllback>);
 * 
 * Example:
 *   float callback(const DELAY_PARAMS &params);
 *   CRepeat<float> callbackRepeat(1000.0f, &callback);
 *
 * To register parameters to be passed to the callback:
 *   callbackRepeat.registerParam(<&data>, <type of data> <size of data>);
 * e.g.
 *   float fValue(13.0f);
 *   callbackRepeat.registerParam(&fValue, TYPE_FLOAT, sizeof(float));
 *
 * To obtain the value of the the parameter passed in the function, you
 * have to dereference to parameter in the callback function, and convert
 * it back to the original type.
 *
 * float callback(const DELAY_PARAMS &params)
 * {
 *    GET_FLOAT_FROM_PARAM(params, 0);
 * }
 *
 * Where params is the variable name of the DELAY_PARAMS parameter, and 0
 * is the index of the parameter to obtain. The parameters are passed in the
 * order they're registered.
 */

#include <vector>
#include <string>

#include "utils.h"
#include "timer.h"

using std::vector;
using utils::PARAM;
using utils::typeDefinitions;

const int REPEAT_INDEFINITELY(-1);

typedef vector<PARAM> DELAY_PARAMS;

template <class _Type>
class CRepeat
{
public:
    CRepeat(float fRepeat, _Type(*pfuncCallback)(DELAY_PARAMS&));
    CRepeat(float fRepeat, _Type(*pfuncCallback)(DELAY_PARAMS&), 
            int nRepeatCount);

    void tick();

    void start();
    void pause();
    void stop(bool bExecute=false);
    bool isRunning() const;

    float getTimeUntilNextCallback() const;
    
    void setRepeatCount(int nRepeatCount);
    int getRepeatCount() const;

    void setRepeatDelay(float fRepeatDelay);
    float getRepeatDelay() const;

    void registerParam(void *pData, utils::TYPE_VALUE nType, int nSize);
    void clearParams();

private:
    float m_fDelay;
    int m_nRepeatCount;
    int m_nDefaultRepeatCount;
    bool m_bActive;
    float m_fPauseTimeOffset;
    CTimer m_LastIterateTime;

    _Type(*m_pfuncCallback)(DELAY_PARAMS&);
    DELAY_PARAMS m_arrParams;
};

template <class _Type>
CRepeat<_Type>::CRepeat(float fRepeat, _Type(*pfuncCallback)(DELAY_PARAMS&)) :
    m_fDelay(fRepeat),
    m_pfuncCallback(pfuncCallback),
    m_nRepeatCount(REPEAT_INDEFINITELY),
    m_nDefaultRepeatCount(REPEAT_INDEFINITELY),
    m_fPauseTimeOffset(0.0f),
    m_bActive(false)
{
    /**
     * Constructor, initialise all the arguments and set active to false, and
     * the repeat to iterate forever. This will not run until the .start()
     * function is called.
     *
     * @param float fRepeat The amount of ms between each repetition.
     * @param _Type(*pfuncCallback)(DELAY_PARAMS) The address of a function
     *     which will be called each
     *     iteration.
     */
}

template <class _Type>
void CRepeat<_Type>::registerParam(void *pData, utils::TYPE_VALUE nType, int nSize)
{
    /**
     * Adds a parameter to the array of params.
     *
     * @param void *pData The address of the variable to be used
     * @param TYPE_VALUE nType The type of the value
     * @param int nSize The size of the value
     */
    utils::PARAM data = {pData, nType, nSize};
    m_arrParams.push_back(data);
}

template <class _Type>
void CRepeat<_Type>::clearParams()
{
    /**
     * Clears all previously registered parameters.
     */
    m_arrParams.clear();
}

template <class _Type>
CRepeat<_Type>::CRepeat(float fRepeat, _Type(*pfuncCallback)(DELAY_PARAMS&),
                        int nRepeatCount) :
    m_fDelay(fRepeat),
    m_pfuncCallback(pfuncCallback),
    m_nRepeatCount(nRepeatCount),
    m_nDefaultRepeatCount(nRepeatCount),
    m_fPauseTimeOffset(0.0f),
    m_bActive(false)
{
    /**
     * Constructor, initialise all the arguments and set active to false, and
     * the repeat to iterate for a specifed amount of times. This will not run 
     * until the .start() function is called.
     *
     * @param float fRepeat The amount of ms between each repetition.
     * @param _Type(*pfuncCallback)(DELAY_PARAMS) The address of a function
     *     which will be called each iteration.
     * @param int nRepeatCount The amount of times to iterate the repeat before
     *     automatically stopping.
     */
}

template <class _Type>
void CRepeat<_Type>::tick()
{
    /**
     * Should be executed when we want to tick the repeat. This checks the time
     * difference between the start and current times. If the time difference
     * is greater than the repeat difference, ensure that we call back the
     * function.
     */ 
    if (isRunning())
    {
        if (m_LastIterateTime.getTimeDifference() >= 
            (m_fDelay - m_fPauseTimeOffset))
        {
            // This object doesn't exist....

            m_fPauseTimeOffset = 0.0f;

            // Overwrite with the current time
            m_LastIterateTime.setTime();

            if (m_nRepeatCount != REPEAT_INDEFINITELY)
            {
                if (--m_nRepeatCount <= 0)
                {
                    // We do not need to repeat any more.
                    stop();
                }
            }

            // We have lapsed over the time limit, call the callback.
            (*m_pfuncCallback)(m_arrParams);
        }
    }
}

template <class _Type>
void CRepeat<_Type>::start()
{
    /**
     * Starts the repeat, and begins counting from the current moment in time.
     */
    if (! isRunning())
    {
        m_nRepeatCount = m_nDefaultRepeatCount;
        m_LastIterateTime.setTime();
        m_bActive = true;
    }
}

template <class _Type>
void CRepeat<_Type>::pause()
{
    /**
     * Pauses the execution of the repeat. The only thing this does is
     * make the active state of the object false. This is equivilent of the
     * stop method, but does not alter the repeat counter.
     */
    if (isRunning())
    {
        m_bActive = false;

        // Set the pause time to the amount of seconds elapsed since the
        // start
        m_fPauseTimeOffset = static_cast<float>(
            m_LastIterateTime.getTimeDifference());
    }
}

template <class _Type>
void CRepeat<_Type>::stop(bool bExecute)
{
    /**
     * Stops the repeat execution. This sets the active state to false, and
     * the amount of repeat counts to 0 so the repeat cannot be restarted until
     * the repeat count is altered.
     */
    m_bActive = false;
    m_nRepeatCount = 0;

    if (bExecute)
    {
        (*m_pfuncCallback)(m_arrParams);
    }
}

template <class _Type>
float CRepeat<_Type>::getTimeUntilNextCallback() const
{
    /**
     * Obtains the amount of seconds till the next callback occurs. Returns
     * -1 if the repeat is not running.
     * 
     * @return float The amount of seconds till the next callback
     */
    if (isRunning())
    {
        return m_fDelay - m_LastIterateTime.getTimeDifference() - 
               m_fPauseTimeOffset;
    }
    return -1.0f;
}

template <class _Type>
void CRepeat<_Type>::setRepeatCount(int nRepeatCount)
{
    /**
     * Sets the amount of time the repeat iterates until it stops.
     *
     * @param int nRepeatCount The amount of times to set to the repeat.
     */
    m_nRepeatCount = nRepeatCount;
    m_nDefaultRepeatCount = nRepeatCount;
    if (nRepeatCount == 0)
    {
        stop();
    }
}

template <class _Type>
int CRepeat<_Type>::getRepeatCount() const
{
    /**
     * @return int The amount of iterations the repeat has left.
     */
    return m_nRepeatCount;
}

template <class _Type>
void CRepeat<_Type>::setRepeatDelay(float fRepeatDelay)
{
    /**
     * Sets the amount of milliseconds between each iteration
     *
     * @param float fRepeatDelay The amount of time between each iteration
     */
    m_fDelay = fRepeatDelay;
}

template <class _Type>
float CRepeat<_Type>::getRepeatDelay() const
{
    /**
     * @return float The amount of milliseconds between each repitition.
     */
    return m_fDelay;
}

template <class _Type>
bool CRepeat<_Type>::isRunning() const
{
    /**
     * @return bool Whether or not the repeat is currently active and running
     */
    return m_bActive;
}

#endif 